//===============================================================================
// Q4Tech Engineering Team
// Mobile Updater Application Block - November 2006
//===============================================================================
// Copyright  Q4Tech Informatica Argentina.  All rights reserved.
// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY
// OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT
// LIMITED TO THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
// FITNESS FOR A PARTICULAR PURPOSE.
//===============================================================================
// The example companies, organizations, products, domain names,
// e-mail addresses, logos, people, places, and events depicted
// herein are fictitious.  No association with any real company,
// organization, product, domain name, email address, logo, person,
// places, or events is intended or should be inferred.
//===============================================================================

using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;
using System.Collections;

namespace Q4Tech.Engineering.Ipc
{
    public class IpcCallClient<TIn, TOut> : IDisposable
    {
        const int chunkSize = 256;
        const int maxMessageCount = 100;
        const String requestQueuePattern = "__{0}";
        const String responseQueuePattern = "__{0}__{1}";
        private String publishName;
        private IPointToPointMessageQueue requestQueue;
        private IPointToPointMessageQueue responseQueue;
        private Guid id;
        private uint timeout;

        public IpcCallClient(IPointToPointMessageQueueFactory factory, String publishName)
        {
            if (publishName == String.Empty)
                throw new ArgumentException("The published name cannot be an empty string.");

            if (publishName == null)
                throw new ArgumentNullException("The published name cannot be null.");

            this.id = Guid.NewGuid();
            this.publishName = publishName;

            requestQueue = factory.Create();
            requestQueue.Initialize(String.Format(requestQueuePattern, publishName), PointToPointMessageQueue.AccessMode.Write, chunkSize, maxMessageCount);

            responseQueue = factory.Create();
            responseQueue.Initialize(String.Format(responseQueuePattern, publishName, Id), PointToPointMessageQueue.AccessMode.Read, chunkSize, maxMessageCount);

            responseQueue.Purge();
        }

        public Guid Id
        {
            get
            {
                return id;
            }
        }

        public uint Timeout
        {
            set
            {
                timeout = value;
            }
        }

        public bool ServerPresent
        {
            get
            {
                return requestQueue.RemotePeerPresent;
            }
        }

        #region Dispose pattern
        private bool disposed = false;

        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        private void Dispose(bool disposing)
        {
            if (!this.disposed)
            {
                if (disposing)
                {
                }
            }

            disposed = true;
        }

        ~IpcCallClient()
        {
            Dispose(false);
        }
        #endregion

        public TOut Exec(TIn requestData)
        {
            IpcCallMessage<TIn> request = new IpcCallMessage<TIn>(Id, Guid.NewGuid(), requestData, chunkSize);
            IpcCallMessage<TOut> response = new IpcCallMessage<TOut>();
            bool keepAdding = true;

            byte[] buffer = request.GetNextChunk();

            while (buffer != null)
            {
                requestQueue.Enqueue(buffer);
                buffer = request.GetNextChunk();
            }
            while (keepAdding)
            {

                if (timeout != 0)
                {
                    int bookmark = System.Environment.TickCount;

                    buffer = responseQueue.Dequeue(timeout);

                    timeout -= (uint)System.Environment.TickCount - (uint)bookmark;

                    if (buffer == null)
                        throw new IpcCallTimeoutException("Timeout expired.");
                }
                else
                    buffer = responseQueue.Dequeue();

                try
                {
                    keepAdding = response.Add(buffer);
                }
                catch (InvalidOperationException)
                {
                }
            }

            return response.Data;
        }
    }
}
